/* 
 * Autores:
 *      Jonathan Lopez  11106
 *      Jorge Martínez  11237
 * 
 * Archivo: Barco.java
 * Última Revision: $Rev: 69 $
 *      Fecha: $Date: 2011-09-19 06:58:38 +0000 (Mon, 19 Sep 2011) $
 */

package Manejo.Transporte;

import Archivador.Informador;
import Manejo.Transporte.Registros.*;
import Archivador.iArchivable;

import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;


/**
 * Representa a un Barco de la Naviera.
 * 
 * @author Jonathan López
 * @author Jorge Martinez
 */
public class Barco implements iArchivable
{
    private double capacidad;
    private ID idNaviera;
    private ID idPuertoActual;
    private Viaje viajeActual;
    
    private final RegContenedores registroContenedores;
    private final RegNavieras registroNavieras;
    private final Registros registros;
    
    private ArrayList<ID> contenedores;
    
    // TODO: Agregar tripulación
    // private ArrayList<Persona> tripulacion;
    
    /**
     * Inicializa el Barco
     * @param capacidad Capacidad, en Toneladas métricas, que puede soportar el
     * Barco. Esta será utilizada para determinar si un Contenedor puede ser
     * agregado al Barco o no.
     * @param viajeActual el Viaje que realizará el Barco, puede ser null
     * si no está realizando ninguno.
     * 
     */
    public Barco( double capacidad, Viaje viajeActual,
            Registros registros)
    {
        this.capacidad = capacidad;
        this.idNaviera = ID.idNulo;
        this.idPuertoActual = ID.idNulo;
        this.viajeActual = viajeActual;
        this.registros = registros;
        this.registroContenedores = registros.obtenerRegistroContenedores();
        this.registroNavieras = registros.obtenerRegistroNavieras();
        
        contenedores = new ArrayList<ID>();
    }
    
    /**
     * Obtiene la capacidad del barco, en Toneladas metricas.
     * @return capacidad del barco, en Toneladas métricas.
     */
    public double obtenerCapacidad()
    {
        return capacidad;
    }
    
    /**
     * Obtiene los contenedores que se encuentran en el Barco actualmente.
     * @return regresa un arreglo con los contenedores que posee el barco
     */
    public ID[] obtenerContenedores()
    {
        return contenedores.toArray( new ID[0] );
    }
    
    /**
     * Regresa la carga total que está cargando el Barco en base a los 
     * Contenedores que lleva.
     * @return carga total, en Toneladas métricas.
     */
    public double obtenerCargaTotal(){
        double carga = 0.0;
        for( ID idContenedor: contenedores ){
            Contenedor contenedor = registroContenedores.obtenerContenedor(idContenedor);
            if( contenedor == null )
                // TODO: Agregar un mensaje al Log
                System.out.println("*ERROR* Barco: No existe el contenedor");
            else
                carga += contenedor.obtenerPeso();
        }
        return carga;
    }
    
    /**
     * Regresa la Naviera a la que se haya registrado este Barco
     * @return Naviera registrada
     */
    public ID obtenerNaviera()
    {
        return idNaviera;
    }
    
    /**
     * Regresa el ID del Puerto en el que se haya atracado el Barco, o idNulo
     * si se encuentra en el agua.
     * @return ID del puerto
     */
    public ID obtenerPuertoActual()
    {
        return idPuertoActual;
    }
    
    /**
     * Asigna un nuevo Puerto en donde se encuentra atracado el Barco
     * @param nuevoPuerto nuevo Puerto a asignar
     * @return puerto anterior
     */
    public ID asignarPuerto( ID nuevoPuerto )
    {
        ID puertoAnterior = idPuertoActual;
        idPuertoActual = nuevoPuerto;
        
        return puertoAnterior;
    }
    
    /**
     * Asigna una nueva Naviera al Barco, este método solo debería de ser
     * llamado en el momento que el Barco se asigna a una Naviera.
     * @param idNuevaNaviera
     * @return naviera anterior
     */
    public final ID cambiarNaviera( ID idNuevaNaviera ){
        ID idNavieraAnterior = this.idNaviera;
        this.idNaviera = idNuevaNaviera;
        return idNavieraAnterior;
    }
    
    /**
     * Regresa una referencia al Viaje que se encuentra realizando actualmente
     * el Barco, o null si no se encuentra realizando ninguno.
     * @return Viaje o null
     */
    public Viaje obtenerViaje(){
        return viajeActual;
    }
    
    /**
     * Cambia el Viaje que va a realizar el Barco, también se puede pasar null.
     * @param nuevoViaje
     * @return viaje anterior
     */
    public Viaje cambiarViaje( Viaje nuevoViaje ){
        Viaje viajeAnterior = viajeActual;
        viajeActual = nuevoViaje;
        
        // Remover el Barco del Puerto anterior
        RegPuertos registroPuertos = registros.obtenerRegistroPuertos();
        RegBarcos registroBarcos = registros.obtenerRegistroBarcos();
        if( viajeAnterior != null ){
            ID idPuerto = viajeAnterior.obtenerPuertoActual();
            Puerto p = registroPuertos.obtenerPuerto(idPuerto);
            
            ID idBarco = registroBarcos.obtenerID(this);
            p.zarparBarco(idBarco);
        }
        
        return viajeAnterior;
    }
    
    /**
     * Trata de agregar un nuevo Contenedor en el Barco según el peso de este.
     * @param contenedorNuevo el ID del nuevo Contenedor a agregar
     * @return regresa true si el Contenedor se pudo cargar, false si no se
     * pudo. Para que el Contenedor se pueda cargar éste no debe de sobrepasar
     * el límite de peso del Barco.
     */
    public boolean cargar( ID contenedorNuevo )
    {
        if( !contenedorNuevo.equals( ID.idNulo ) ){
            // Validar el contenedor
            Contenedor contenedor = registroContenedores.obtenerContenedor(contenedorNuevo);
            if( contenedor == null )
                return false;
            
            // Verificar que el peso nuevo no sobrepase el máximo
            double carga = obtenerCargaTotal();
            if( carga + contenedor.obtenerPeso() > obtenerCapacidad() )
                return false;
            
            // Agregar el Contenedor
            contenedores.add( contenedorNuevo );
            return true;
        } else
            return false;
    }
    
    /**
     * Trata de agregar un nuevo Contenedor en el Barco según el peso de este.
     * Dado que recibe una referencia a un Contenedor éste NO DEBE DE HABERSE
     * REGISTRADO CON EL REGISTRO DE CONTENEDORES. Si el Barco puede llevar el Contenedor
     * entonces es automáticamente agregado en el Registro de Contenedores.
     * @param contenedorNuevo nuevo Contenedor a agregar
     * @return regresa true si el Contenedor se pudo cargar, false si no se
     * pudo. Para que el Contenedor se pueda cargar éste no debe de sobrepasar
     * el límite de peso del Barco.
     */
    public boolean cargar( Contenedor contenedorNuevo ){
        if( contenedorNuevo == null )
            return false;
            
        // Verificar que el peso nuevo no sobrepase el máximo
        double carga = obtenerCargaTotal();
        if( carga + contenedorNuevo.obtenerPeso() > obtenerCapacidad() )
            return false;

        // Agregar el Contenedor
        ID idContenedor = registroContenedores.agregarContenedor(contenedorNuevo);
        contenedores.add( idContenedor );
        return true;
    }
    
    /**
     * El barco descarga automaticamente los contenedores que debe dejar en el 
     * puerto donde esta atracado.
     * @return regresa un arreglo con los contenedores a descargar en el puerto
     */
    public ID[] descargar() 
    {
        ArrayList<ID> contenedoresPorDescargar = new ArrayList<ID>();
        
        for (Iterator<ID> it = contenedores.iterator(); it.hasNext();) {
            ID idContenedor = it.next();
            Contenedor contenedor = registroContenedores.obtenerContenedor(idContenedor);
            if( idPuertoActual.equals( contenedor.obtenerPuertoDestino() )){
                contenedoresPorDescargar.add( idContenedor );
                it.remove();
            }
            
        }
        
        return contenedoresPorDescargar.toArray( new ID[0] );
    }

    /**
     * Regresa una representación String del Barco
     * @return String
     */
    @Override
    public String toString()
    {
        RegPuertos registroPuertos = registros.obtenerRegistroPuertos();
        String nombreNaviera = registroNavieras.obtenerNombre(idNaviera);
        if( nombreNaviera.equals("") )
            nombreNaviera = "<No asignada>";
        
        String nombrePuerto = registroPuertos.obtenerNombre(idPuertoActual);
        if( nombrePuerto.equals("") )
            nombrePuerto = "<No asignado>";
        
        String sContenedores = "";
        if( !contenedores.isEmpty() ){
            sContenedores += ", contenedores=" + contenedores;
        }
        
        return "Barco{" + "capacidad=" + capacidad +
                ", naviera=" + nombreNaviera +
                ", puertoActual=" + nombrePuerto +
                ", viajeActual=" + (viajeActual==null?"<No asignado>":viajeActual) +
                sContenedores + '}';
    }
    
    /**
     * Regresa un línea de texto para ser guardada en un medio persistente.
     * @return String
     * @see iArchivable#guardar() 
     */
    @Override
    public String guardar(){
        // Orden: capacidad, naviera, puertoActual, contenedores y [PARTE] viajeActual
        String linea = "[";
        linea += capacidad + separador;
        linea += idNaviera.guardar() + separador;
        linea += idPuertoActual.guardar() + separador;
        
        for( ID cont: contenedores ){
            linea += cont.guardar() + separador;
        }
        linea += "]";
        
        /* Agregar viajeActual como otro objeto */
        linea += separadorObjetos;
        linea += viajeActual == null? "null":viajeActual.guardar();
        
        return linea;
    }
    
    /**
     * Genera un nuevo Barco a partir de una línea de texto creada a través
     * del método guardar()
     * @param linea línea a parsear
     * @param registroContenedores
     * @param registroRutas
     * @param registroPuertos
     * @return nuevo Barco con los datos leídos desde la línea dada, o null
     * si no se pudo interpretar la línea.
     */
    public static Barco parseBarco( String linea,
            Registros registros ){
        
        // Separar objetos
        String[] objetos = linea.split( separadorObjetos );
        if( objetos.length != 2 )
            return null;
        String srBarco = objetos[0];
        String srViajeActual = objetos[1];
        
        // Validar la linea
        if( !Archivador.Util.validarLinea(srBarco) )
            return null;
        
        // Eliminar los corchetes
        srBarco = Archivador.Util.eliminarExtremos(srBarco);
        if( srBarco.equals( "" ) )
            return null;
        
        // Partir el String
        String partes[] = srBarco.split( separador );
        int minimoPartes = 3;
        if( partes.length < minimoPartes )
            return null;
        
        // Parsear cada elemento
        // Orden: capacidad, naviera, puertoActual, contenedores y [PARTE] viajeActual
        double _capacidad = 0;
        ID _naviera = ID.idNulo;
        ID _puertoActual = ID.idNulo;
        ArrayList<ID> _contenedores = new ArrayList<ID>();
        
        for (int n = 0; n != partes.length; n++) {
            if( n == 0 )
                _capacidad = Double.parseDouble(partes[n]);
            else if( n == 1 )
                _naviera = ID.parseID(partes[n]);
            else if( n == 2 )
                _puertoActual = ID.parseID( partes[n] );
            else
                _contenedores.add( ID.parseID(partes[n]) );
        }
        
        // Parsear el viaje
        Viaje _viajeActual = null;
        if( !srViajeActual.equals("null") ){
            _viajeActual = Viaje.parseViaje( srViajeActual, registros );
            if( _viajeActual == null )
                return null;
        }
        
        Barco nuevoBarco = new Barco( _capacidad, _viajeActual,
                registros);
        nuevoBarco.idNaviera = _naviera;
        nuevoBarco.idPuertoActual = _puertoActual;
        nuevoBarco.contenedores = _contenedores;
        
        return nuevoBarco;
    }
    
    /**
     * Punto de entrada para las pruebas unitarias de la clase Barco.
     * @param args
     */
    public static void main( String[] args ){
        java.util.Random gen = new java.util.Random();
        Registros registros = new Registros();
        RegContenedores registroContenedores = registros.obtenerRegistroContenedores();
        RegNavieras registroNavieras = registros.obtenerRegistroNavieras();
        RegBarcos registroBarcos = registros.obtenerRegistroBarcos();
        
        Naviera poseedor = new Naviera( "GUATEMALA", registros );
        ID idNaviera = registroNavieras.agregarNaviera(poseedor, "MAERSK");
        
        // Generar varias pruebas
        int pruebas = 10;
        int fallos = 0;
        Barco[] barcos = new Barco[pruebas];
        for (int n = 0; n != pruebas; n++) {
            barcos[n] = new Barco( gen.nextDouble(),
                    new Viaje( GeneradorID.nuevoID(), new Date(), registros ),
                    registros);
            
            // Generar los contenidos
            Producto[] producto = new Producto[1];
            producto[0] = new Producto( gen.nextDouble(), "Yoyos-");
            ID[] carga = new ID[2];
            Contenedor c1 = new Contenedor( producto, ID.idNulo, "YO", registros);
            Contenedor c2 = new Contenedor( producto, ID.idNulo, "YO", registros);
            carga[0] = registroContenedores.agregarContenedor(c1);
            carga[1] = registroContenedores.agregarContenedor(c2);
            barcos[n].cargar(carga[0]);
            barcos[n].cargar(carga[1]);
            
            String lineaActual = barcos[n].guardar();
            Barco nuevoBarco = Barco.parseBarco(lineaActual, registros );
            
            boolean exito = barcos[n].toString().equals(nuevoBarco.toString() );
            if( !exito ) ++fallos;
            
            System.out.println(lineaActual + " && " + exito );
            System.out.println("\t" + barcos[n] );
            System.out.println("\t" + nuevoBarco );
            
            registroBarcos.agregarBarco( barcos[n], Integer.toHexString(gen.nextInt()) );
        }
        System.out.println("Fallos: " + fallos );
        
        registroBarcos.guardarTodo();
        Informador.guardarCambios();
    }
}
